<?php
/**
 * Provides a read-only registry
 *
 * Stores values statically so they can be retrieved later. Only slightly better
 * than global variables. This registry is read-only. So once a value is set,
 * it cannot be changed.
 *
 * Provides a means for lazy loading values via callback functions. That is, the
 * value for a registry key doesn't have to be determined before setting it. A
 * callback function can be used to get the value if and only if the registry
 * key is actually used. That way an expensive operation to determine a registry
 * value can be skipped altogether if the value is never used.
 *
 * Objects clones can be returned instead of the actual stored objects. This is
 * in keeping with a read-only registry. Changes in state to a returned object
 * won't affect the real object stored in the registry.
 *
 * Copyright (C) 2008  Sean Hickey
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Sean Hickey <sean@headzoo.com>
 * @version $Id: Registry.php 372 2008-07-03 04:06:39Z sean $
 * @package Alinks_Registry
 */
class Alinks_Registry
{
	/**
	 * The registry values
	 *
	 * @var array
	 */
	protected static $_values = array();
	
	/**
	 * Whether objects returned by get() should be clones of the
	 * object instead of the real deal
	 *
	 * @var bool
	 */
	protected static $_get_clones = false;
	
	/**
	 * Constructor
	 *
	 * The registry should only be used statically. Instances should not be
	 * created.
	 */
	protected function __construct() {}
	
	/**
	 * Sets whether get() should return clones of stored objects
	 *
	 * In keeping with the idea that the registry is read-only, the get()
	 * method can return clones of objects so that changes to the returned
	 * object's state don't affect the originally stored value.
	 *
	 * The return value is the previous "get clones" setting.
	 *
	 * @param bool $value To return clones or not
	 */
	public static function setReturnClones($value)
	{
		$old = self::$_get_cones;
		self::$_get_clones = $value;
		return $old;
	}
	
	/**
	 * Returns whether the get() method is returning clones of objects or not
	 *
	 * @return bool
	 */
	public static function getReturnClones()
	{
		return self::$_get_clones;
	}
	
	/**
	 * Returns if a value exists for a given key
	 *
	 * @param string $key The registry key
	 * @return bool
	 */
	public static function exists($key)
	{
		return array_key_exists($key, self::$_values);
	}
	
	/**
	 * Returns the value for the given key
	 *
	 * Given the key of a registry value, the method returns the
	 * value, or returns the default value if the key does not
	 * exist in the registry.
	 *
	 * Returns null if the key isn't set, and the default value is
	 * not set.
	 *
	 * If $key is null, then all the registry values will be returned.
	 *
	 * @param string $key The registry key
	 * @param mixed $default Default value
	 * @return mixed
	 */
	public static function get($key = null, $default = null)
	{
		if (null === $key) {
			$value = array();
			foreach(array_keys(self::$_values) as $key) {
				$value[$key] = self::get($key);
			}
		} else if (!self::exists($key)) {
			$value = $default;
		} else if (self::_isLazyLoad($key)) {
			self::$_values[$key] = call_user_func(self::$_values[$key]->__lazy_load_callback);
			$value = self::$_values[$key];
		} else if (self::$_get_clones && is_object(self::$values[$key])) {
			$value = clone self::$_values[$key];
		} else {
			$value = self::$_values[$key];
		}
		
		return $value;
	}
	
	/**
	 * Sets a registry value
	 *
	 * Sets a registry value, associating it with the given key. Throws
	 * an Alinks_Exception if the registry key already exists because the registry
	 * is read-only, and values can't be changed.
	 *
	 * The registry provides a means for lazy loading the values. That is, the
	 * value for the key won't actually be determined until the value is
	 * requested with the get() method. This lets us skip determining values
	 * (which can sometimes be an expensive operation) if that value is
	 * never used.
	 *
	 * @param string $key The registry key
	 * @param mixed $value The value
	 * @param string|array $callback Callback method for lazy loading
	 * @return bool
	 * @throws Alinks_Exception
	 */
	public static function set($key, $value = null, $callback = null)
	{
		if (null === $value && null === $callback) {
			/**
			 * @see Alinks_Registry_Exception
			 */
			require_once('Alinks/Registry/Exception.php');
			throw new Alinks_Registry_Exception('Must set either value or callback. Both cannot both be null');
		}
		if (array_key_exists($key, self::$_values)) {
			/**
			 * @see Alinks_Registry_Exception
			 */
			require_once('Alinks/Registry/Exception.php');
			throw new Alinks_Registry_Exception("The value for '$key' is already set and cannot be changed");
		}
		if ($value instanceof stdClass && isset($value->__lazy_load_callback)) {
			/**
			 * @see Alinks_Registry_Exception
			 */
			require_once('Alinks/Registry/Exception.php');
			throw new Alinks_Registry_Exception("Cannot use the value stdClass::__lazy_load_callback");
		}
		
		if (null !== $callback) {
			$value = new stdClass();
			$value->__lazy_load_callback = $callback;
		}
		
		self::$_values[$key] = $value;
		return true;
	}
	
	/**
	 * Returns if the value for a key is a lazy loading instruction
	 *
	 * @param string $key The registry key
	 * @return bool
	 */
	protected static function _isLazyLoad($key)
	{
		return (self::$_values[$key] instanceof stdClass && isset(self::$_values[$key]->__lazy_load_callback));
	}
}